﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using Nova.Parsing;
using Nova.Rendering;

namespace Nova.CodeDOM
{
    /// <summary>
    /// The common base class of the <see cref="Case"/> and <see cref="Default"/> statements (of a <see cref="Switch"/>).
    /// </summary>
    public abstract class SwitchItem : BlockStatement, INamedCodeObject
    {
        #region /* CONSTRUCTORS */

        protected SwitchItem(CodeObject body)
            : base(body, true)  // Allow null bodies for Case/Default
        { }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The name of the <see cref="SwitchItem"/>.
        /// </summary>
        public virtual string Name
        {
            get { return null; }
        }

        /// <summary>
        /// The descriptive category of the code object.
        /// </summary>
        public string Category
        {
            get { return "switch item"; }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Create a reference to the <see cref="SwitchItem"/>.
        /// </summary>
        /// <param name="isFirstOnLine">True if the reference should be displayed on a new line.</param>
        /// <returns>A <see cref="SwitchItemRef"/>.</returns>
        public override SymbolicRef CreateRef(bool isFirstOnLine)
        {
            return new SwitchItemRef(this, isFirstOnLine);
        }

        /// <summary>
        /// Add the <see cref="CodeObject"/> to the specified dictionary.
        /// </summary>
        public void AddToDictionary(NamedCodeObjectDictionary dictionary)
        {
            // Prefix Labels and SwitchItems with a ':' to segregate them
            dictionary.Add(ParseTokenTerminator + Name, this);
        }

        /// <summary>
        /// Remove the <see cref="CodeObject"/> from the specified dictionary.
        /// </summary>
        public void RemoveFromDictionary(NamedCodeObjectDictionary dictionary)
        {
            dictionary.Remove(ParseTokenTerminator + Name, this);
        }

        /// <summary>
        /// Get the full name of the <see cref="INamedCodeObject"/>, including any namespace name.
        /// </summary>
        public string GetFullName(bool descriptive)
        {
            return Name;
        }

        /// <summary>
        /// Get the full name of the <see cref="INamedCodeObject"/>, including any namespace name.
        /// </summary>
        public string GetFullName()
        {
            return Name;
        }

        #endregion

        #region /* PARSING */

        /// <summary>
        /// The token used to parse the end of the item.
        /// </summary>
        public new const string ParseTokenTerminator = ":";

        protected SwitchItem(Parser parser, CodeObject parent)
            : base(parser, parent)
        { }

        protected void ParseTerminatorAndBody(Parser parser)
        {
            ParseTerminator(parser);  // Parse ':'

            // Parse the body until we find the next 'case' or 'default' (or the end of the 'switch' block).
            // If the body starts with a '{', then it will be parsed until it finds the '}'.
            new Block(out _body, parser, this, false, Case.ParseToken, Default.ParseToken);
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// True if the <see cref="Statement"/> has an argument.
        /// </summary>
        public override bool HasArgument
        {
            get { return true; }
        }

        /// <summary>
        /// True if the <see cref="BlockStatement"/> always requires braces.
        /// </summary>
        public override bool HasBracesAlways
        {
            get { return false; }
        }

        /// <summary>
        /// Determines if the body of the <see cref="BlockStatement"/> should be formatted with braces.
        /// </summary>
        public override bool ShouldHaveBraces()
        {
            // It's actually really hard to determine if a SwitchItem should have braces, because they only need
            // them if one or more LocalDecls are declared and none of them are accessed by any following SwitchItems
            // up until the next one that has braces.  For now, just go by the current status, leaving it up to the
            // coder to get it right.
            return HasBraces;
        }

        /// <summary>
        /// True if the <see cref="BlockStatement"/> requires an empty statement if it has an empty block with no braces.
        /// </summary>
        public override bool RequiresEmptyStatement
        {
            get { return false; }
        }

        /// <summary>
        /// The terminator character for the <see cref="Statement"/>.
        /// </summary>
        public override string Terminator
        {
            get { return ParseTokenTerminator; }
        }

        /// <summary>
        /// True if the <see cref="Statement"/> has a terminator character by default.
        /// </summary>
        public override bool HasTerminatorDefault
        {
            get { return true; }
        }

        /// <summary>
        /// Determine a default of 1 or 2 newlines when adding items to a <see cref="Block"/>.
        /// </summary>
        public override int DefaultNewLines(CodeObject previous)
        {
            // Always default to no blank lines before switch items
            return 1;
        }

        #endregion

        #region /* RENDERING */

        /// <summary>
        /// Render as a SwitchItemRef target.
        /// </summary>
        public void AsTextGotoTarget(CodeWriter writer, RenderFlags flags)
        {
            AsTextStatement(writer, flags);
            if (HasArgument)
            {
                AsTextArgumentPrefix(writer, flags);
                AsTextArgument(writer, flags);
            }
        }

        protected override void AsTextSuffix(CodeWriter writer, RenderFlags flags)
        {
            // Render the terminator even when in Description mode (for references)
            AsTextTerminator(writer, flags);
        }

        protected override void AsTextAfter(CodeWriter writer, RenderFlags flags)
        {
            if (!flags.HasFlag(RenderFlags.NoPostAnnotations))
                AsTextAnnotations(writer, AnnotationFlags.IsPostfix, flags);

            if (_body != null && !flags.HasFlag(RenderFlags.Description))
            {
                // Check for alignment of the body (ignore if empty or it doesn't fit the pattern)
                if (IsFirstOnLine && _body != null && _body.Count > 0 && !_body.IsFirstOnLine && _body.IsSingleLine)
                {
                    int columnWidth = writer.GetColumnWidth(Parent, 0);
                    if (columnWidth > 0)
                    {
                        int padding = columnWidth - AsTextLength(RenderFlags.Description | RenderFlags.LengthFlags);
                        if (padding > 0)
                            writer.Write(new string(' ', padding));
                    }
                }

                _body.AsText(writer, flags);
            }
        }

        #endregion
    }
}
